"""

    File: amf.py
    Description: 
    
        This module parses raw data into parsed AMFobject data.

    Author: Kyle Vogt
    Date  : June 5th, 2007
    Copyright (c) 2007, Justin.tv, Inc.
    
"""

from twisted.python import log
import struct, traceback

CHUNK_SIZE =             0x01
BYTES_READ =             0x03
PING =                     0x04
SERVER_BW =             0x05
CLIENT_BW =             0x06
AUDIO =                 0x08
VIDEO =                 0x09
FLEX_STREAM =             0x0F
FLEX_SHARED_OBJECT =     0x10
FLEX_MESSAGE =             0x11
NOTIFY =                 0x12
SHARED_OBJECT =         0x13
INVOKE =                 0x14

META_CHANNEL =             0x02
CONNECTION_CHANNEL =     0x03
BW_CHANNEL =            0x04
EVENT_CHANNEL =         0x07

class Amf:
    
    def __init__(self):
        self.data = ""
        self.pointer = 0
        self.length = 0
        self.id = 1
        self.elements = []
        
    def decode(self, data):
        self.data = data
        self.pointer = 0
        items = []
        while self.pointer < len(data):
            item = self._decode()
            if item != "END": items.append(item)
        return items        

    def encode(self, elements=False):
        if not elements: elements = self.elements
        # Create element data
        edata = ""
        # Write elements
        for value in elements:
            edata += self._encode(value)
        return edata
        
    def _encode(self, value):
        edata = ""
        try:
            if isinstance(value, float) or isinstance(value, int):
                edata += chr(0)
                edata += struct.pack("!d", value)
            elif isinstance(value, bool):
                edata += chr(1)
                if value == True: edata += chr(1)
                else: edata += chr(0)
            elif isinstance(value, str):
                edata += chr(2)
                edata += struct.pack("!h", len(value))
                edata += value
            elif isinstance(value, dict):
                edata += chr(3)
                # Hack to force ordering of NetStatus keys
                for key in ['level', 'code', 'description']:
                    if key in value.keys():
                        edata += struct.pack("!h", len(key))
                        edata += key
                        edata += self._encode(value[key])
                        del value[key]
                # Encode any remaining keys
                for key, val in value.items():
                    edata += struct.pack("!h", len(key))
                    edata += key
                    edata += self._encode(val)
                edata += chr(0) * 2
                edata += chr(9)
            elif value == None:
                edata += chr(5)
            elif isinstance(value, dict):
                edata += chr(8)
                edata += struct.pack("!i", len(value))                
                for key, val in value.items():
                    edata += struct.pack("!h", len(key))
                    edata += key
                    edata += self._encode(val)
                edata += chr(0) * 2
                edata += chr(9)
        except:
            traceback.print_exc()
        return edata
                
    def _decode(self):
        dataType = ord(self.data[self.pointer])
        self.pointer += 1
        if dataType == 0x00: value = self.readNumber()
        elif dataType == 0x01: value = self.readBoolean()
        elif dataType == 0x02: value = self.readString()
        elif dataType == 0x03: value = self.readObject()
        elif dataType == 0x05: value = None
        elif dataType == 0x06:
            value = None
            self.pointer += 3
        elif dataType == 0x08: value = self.readMixedArray()
        elif dataType == 0x09: value = 'END'
        else: value = "END"
        return value
            
    def readNumber(self):
        try:
            value = struct.unpack("!d", self.data[self.pointer:self.pointer+8])[0]
        except:
            self.pointer += len(self.data[self.pointer:self.pointer+8])
            return 0.0
        self.pointer += 8
        return value

    def readLong(self):
        try:
            value = struct.unpack("!i", self.data[self.pointer:self.pointer+4])[0]
        except:
            self.pointer += len(self.data[self.pointer:self.pointer+4])
            return 0.0
        self.pointer += 4
        return value
    
    def readBoolean(self):
        try:
            value = ord(self.data[self.pointer])
            self.pointer += 1
            return value
        except:
            self.pointer += 1
            return False

    def readString(self):
        try: length = struct.unpack("!h", self.data[self.pointer:self.pointer+2])[0]
        except: return ''
        self.pointer += 2
        if length > 0:
            value = self.data[self.pointer:self.pointer+length]
            self.pointer += length
        else: value = ''
        return value

    def readUnicodeString(self):
        try: length = struct.unpack("!h", self.data[self.pointer:self.pointer+2])[0]
        except: return ''
        self.pointer += 2
        if length > 0:
            value = self.data[self.pointer:self.pointer+length]
            self.pointer += length
        else: value = ''
        return value
        
    def readObject(self):
        obj = {}
        while True:
            key = self.readUnicodeString()
            try:
                value = self._decode()
                if value != 'END':
                    obj[key] = value
                else: break
            except:    break
        return obj
        
    def readMixedArray(self):
        obj = {}
        elements = self.readLong()
        while True:
            key = self.readString()
            try:
                value = self._decode()
                if value != 'END':
                    obj[key] = value
                else: break
            except:    break
        return obj
    

